package com.fuint.common.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.fuint.common.chainlog.ChainLog;
import com.fuint.common.enums.OrderTypeEnum;
import com.fuint.common.service.OrderService;
import com.fuint.common.service.SettingService;
import com.fuint.common.service.StoreService;
import com.fuint.common.service.SybPayService;
import com.fuint.common.util.LogUtils;
import com.fuint.common.util.PayUtil;
import com.fuint.common.vo.SybPayConfigVO;
import com.fuint.framework.exception.BusinessCheckException;
import com.fuint.framework.web.ResponseObject;
import com.fuint.repository.model.MtOrder;
import com.fuint.repository.model.MtSetting;
import com.fuint.repository.model.MtStore;
import com.fuint.repository.model.MtUser;
import com.fuint.utils.JsonFormatUtil;
import com.fuint.utils.syb.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

import static com.fuint.common.Constants.PAY_CHANNEL_PLATFORM;
import static com.fuint.common.Constants.PAY_CHANNEL_STORE;
import static com.fuint.utils.syb.SybConstants.*;

@Slf4j
@Service
public class SybPayServiceImpl implements SybPayService {

    private final RestTemplate restTemplate = new RestTemplate();
    private final StoreService storeService;
    private final SettingService settingService;
    private final String wxAppId;

    private final OrderService orderService;

    @Autowired
    SybPayServiceImpl(@Value("${wxpay.appId}") String wxAppId, StoreService storeService, SettingService settingService, OrderService orderService) {
        this.storeService = storeService;
        this.settingService = settingService;
        this.wxAppId = wxAppId;
        this.orderService = orderService;
    }

    private static Map<String, String> settingList2Map(List<MtSetting> settingList) {
        Map<String, String> map = new HashMap<>();
        if (CollUtil.isNotEmpty(settingList)) {
            for (MtSetting setting : settingList) {
                if (Objects.isNull(setting)) {
                    continue;
                }
                map.put(setting.getName(), setting.getValue());
            }
        }
        return map;
    }


    @Override
    public ResponseObject createPrepayOrder(
            MtUser userInfo,
            MtOrder orderInfo,
            Integer payAmount,
            String authCode,
            Integer giveAmount,
            String ip,
            String platform
    ) throws BusinessCheckException {
        // 读取配置
        SybPayConfigVO config = getConfig(orderInfo.getStoreId());

        Integer orderId = orderInfo.getId();
        String goodsInfo = orderInfo.getOrderSn();
        if (orderInfo.getType().equals(OrderTypeEnum.PRESTORE.getKey())) {
            goodsInfo = OrderTypeEnum.PRESTORE.getValue();
        }
        // 读取数据库 TL 配置
        String appId = config.getAppId();
        String cusId = config.getCusId();
        String orgId = config.getOrgId();
        String callbackUrl = config.getCallbackUrl();
        String wxAppId = config.getWxAppId();
        // 金额
        long trxAmt = Long.valueOf(payAmount);
        // 订单号
        String sn = orderInfo.getOrderSn();
        String body = goodsInfo;
        String remark = "";
        SybYunPayDTO dto = new SybYunPayDTO();
        try {
            dto.payInit(orgId, trxAmt, sn, body, remark, appId, cusId, ip, wxAppId, config.getSybSm2PrivateKey(), callbackUrl);
            // Map<String, String> pay = yunPay(dto);
            // pay.put("result", "0000");
            Map<String, String> pay = PayUtil.yunPay(dto);
            ResponseObject responseObject = new ResponseObject(200, "TL支付接口返回成功", pay);
            LogUtils.info("TL YunPay createPrepayOrder outParams {}", JsonFormatUtil.toJsonStr(responseObject));
            return responseObject;
        } catch (Exception e) {
            LogUtils.error("支付失败:", e);
        }

        // 更新支付渠道
        MtOrder originOrder = orderService.getOrderInfo(orderId);
        if (Objects.nonNull(originOrder)) {
            originOrder.setPayChannel(config.getPayChannel());
            orderService.updateOrder(originOrder);
        }

        return null;
    }

    @Override
    public ResponseObject createPrepayOrderForWxCashier(MtUser userInfo, MtOrder orderInfo, Integer payAmount, String authCode, Integer giveAmount, String ip, String platform) throws BusinessCheckException {
        // 读取配置
        SybPayConfigVO config = getConfig(orderInfo.getStoreId());
        // 读取订单信息
        String goodsInfo = orderInfo.getOrderSn();
        if (orderInfo.getType().equals(OrderTypeEnum.PRESTORE.getKey())) {
            goodsInfo = OrderTypeEnum.PRESTORE.getValue();
        }
        // 读取数据库 TL 配置
        String appId = config.getAppId();
        String cusId = config.getCusId();
        String orgId = config.getOrgId();
        String callbackUrl = config.getCallbackUrl();
        // 金额
        long trxAmt = Long.valueOf(payAmount);
        String sn = orderInfo.getOrderSn();
        String body = goodsInfo;
        String remark = "";
        String acct = userInfo.getOpenId();
        SybWxPayDTO dto = new SybWxPayDTO();
        try {
            dto.payCashierInit(orgId, trxAmt, sn, body, remark, acct, appId, cusId, config.getSybSm2PrivateKey(), callbackUrl);
            LogUtils.info("AlipayService createPrepayOrder inputParam {}", JsonFormatUtil.toJsonStr(dto));
            ResponseObject responseObject = new ResponseObject(200, "TL支付接口返回成功", dto);
            LogUtils.info("TL wxCashier Pay createPrepayOrder outParams {}", JsonFormatUtil.toJsonStr(responseObject));
            return responseObject;
        } catch (Exception e) {
            LogUtils.error("支付失败:", e);
        }

        return null;
    }

    @Override
    public ResponseObject createPrepayOrderForWx(MtUser userInfo, MtOrder orderInfo, Integer payAmount, String authCode, Integer giveAmount, String ip, String platform) throws BusinessCheckException {
        // 读取配置
        SybPayConfigVO config = getConfig(orderInfo.getStoreId());
        // 读取订单信息
        String goodsInfo = orderInfo.getOrderSn();
        if (orderInfo.getType().equals(OrderTypeEnum.PRESTORE.getKey())) {
            goodsInfo = OrderTypeEnum.PRESTORE.getValue();
        }
        // 读取数据库 TL 配置
        String appId = config.getAppId();
        String cusId = config.getCusId();
        String orgId = config.getOrgId();
        String callbackUrl = config.getCallbackUrl();
        // 金额
        long trxAmt = Long.valueOf(payAmount);
        String sn = orderInfo.getOrderSn();
        String body = goodsInfo;
        String remark = "";
        String acct = userInfo.getOpenId();
        SybWxPayDTO dto = new SybWxPayDTO();
        try {
            dto.payInit(orgId, trxAmt, sn, body, remark, acct, appId, cusId, config.getSybSm2PrivateKey(), callbackUrl);
            // Map<String, String> pay = doRequest(SYB_APIURL + PAY_PATH, dto);
            // pay.put("result", "0000");

            // === 使用收银台方式对接后，服务端不直接请求接口，而是吧组合好的参数给前端，让前端做跳转
            // Map<String, String> pay = PayUtil.wxPay(dto);
            // ResponseObject responseObject = new ResponseObject(200, "TL支付接口返回成功", pay);
            // LogUtils.info("AlipayService createPrepayOrder outParams {}", JsonFormatUtil.toJsonStr(pay));
            // ===

            LogUtils.info("AlipayService createPrepayOrder inputParam {}", JsonFormatUtil.toJsonStr(dto));
            ResponseObject responseObject = new ResponseObject(200, "TL支付接口返回成功", dto);
            LogUtils.info("TL WxPay createPrepayOrder outParams {}", JsonFormatUtil.toJsonStr(responseObject));
            return responseObject;
        } catch (Exception e) {
            LogUtils.error("支付失败:", e);
        }

        return null;
    }


    @Override
    public SybPayConfigVO getConfig(Integer storeId) throws BusinessCheckException {
        // 查全局，如果 enableGlobalSybPay 为 true 则使用全局
        List<MtSetting> setting = settingService.getSettingList(0, "common");
        Map<String, String> configMap = settingList2Map(setting);
        String useGlobalSybPay = configMap.get("useGlobalSybPay");
        String orgId = configMap.get("sybOrgId");
        String cusId = configMap.get("sybCusId");
        String appId = configMap.get("sybAppId");
        String sybSm2PrivateKey = configMap.get("sybSm2PrivateKey");
        String sybSm2PublicKey = configMap.get("sybSm2PublicKey");
        String callbackUrl = configMap.get("payCallbackUrl");
        String payChannel = PAY_CHANNEL_PLATFORM;
        // 如果不使用全局，则使用店铺的支付配置
        // 查店铺
        if (Objects.nonNull(storeId)) {
            MtStore mtStore = storeService.queryStoreById(storeId);
            if (Objects.nonNull(mtStore)) {
                String enableSyb = mtStore.getEnableSyb();
                String enableWxPay = mtStore.getEnableWxPay();
                String storeSybOrgId = mtStore.getSybOrgId();
                if ("true".equals(enableSyb)) {
                    // 支付渠道设置为店铺
                    payChannel = PAY_CHANNEL_STORE;
                    if (StrUtil.isNotEmpty(storeSybOrgId)) {
                        orgId = storeSybOrgId;
                    }
                    String storeSybCusId = mtStore.getSybCusId();
                    if (StrUtil.isNotEmpty(storeSybCusId)) {
                        cusId = storeSybCusId;
                    }
                    String storeAppId = mtStore.getSybAppId();
                    if (StrUtil.isNotEmpty(storeAppId)) {
                        appId = storeAppId;
                    }
                    String storeSybSm2PrivateKey = mtStore.getSybSm2PrivateKey();
                    if (StrUtil.isNotEmpty(storeSybSm2PrivateKey)) {
                        sybSm2PrivateKey = storeSybSm2PrivateKey;
                    }
                    String storeSybSm2PublicKey = mtStore.getSybSm2PublicKey();
                    if (StrUtil.isNotEmpty(storeSybSm2PublicKey)) {
                        sybSm2PublicKey = storeSybSm2PublicKey;
                    }
                }
            }
        }

        // TODO 先写死，后面放到DB里
        return new SybPayConfigVO(
                appId,
                cusId,
                orgId,
                SybConstants.SIGN_TYPE,
                sybSm2PrivateKey,
                sybSm2PublicKey,
                callbackUrl,
                payChannel,
                "wx01a890691c1eb0d0",
                "1113333fdc038b62b8303ac5b078d04c"
        );
    }

    @Override
    public Boolean doRefund(Integer storeId, String orderSn, BigDecimal totalAmount, BigDecimal refundAmount, String platform) throws BusinessCheckException {
        ChainLog.ChainLogBuilder chainLogBuilder = ChainLog.builder().title("TL退款");
        String param = String.format("storeId:%s,orderSn:%s,totalAmount:%s,refundAmount:%s,platform:%s", storeId, orderSn, totalAmount, refundAmount, platform);
        SybPayConfigVO config = getConfig(storeId);
        // 换算成分
        Long trxAmt = refundAmount.multiply(new BigDecimal(100)).longValue();
        String refundSn = "R" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) + RandomUtil.randomInt(4);
        SybYunRefundDTO dto = new SybYunRefundDTO();
        try {
            dto.init(config.getOrgId(), trxAmt, refundSn, orderSn, config.getCusId(), config.getAppId(), config.getSybSm2PrivateKey());
            param += String.format("\nDTO.init:%s", JsonFormatUtil.toJsonStr(dto));
            chainLogBuilder.paramsLog(param);
            Map<String, String> stringStringMap = doRequest(SYB_YUN_REFUND_API, dto);
            chainLogBuilder.successLog("\n" + JsonFormatUtil.toJsonStr(stringStringMap));
        } catch (Exception e) {
            chainLogBuilder.errorLog(e);
            throw new BusinessCheckException("退款失败");
        }
        chainLogBuilder.build().print();
        return true;
    }

    public Map<String, String> yunPay(SybYunPayDTO dto) throws Exception {
        TreeMap<String, String> params = SybUtil.parseEntity(dto);
        LogUtils.info("请求参数:", JsonFormatUtil.toJsonStr(params));
        MultiValueMap<String, String> multiParams = SybPayUtils.convert(params);
        // 请求头
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(multiParams, headers);
        // 云闪付地址
        String url = SYB_YUN_PAY_API + YUN_PAY_PATH;
        LogUtils.info("请求地址: POST ", url);
        ResponseEntity<String> response = restTemplate.exchange(
                url,
                HttpMethod.POST,
                requestEntity,
                String.class
        );
        String responseBody = response.getBody();
        LogUtils.info("响应结果:", JSONUtil.toJsonPrettyStr(responseBody));
        return JSONUtil.toBean(responseBody, Map.class);
    }

    /**
     * 执行请求
     *
     * @param url 请求地址
     * @param dto 入参
     * @return Map<String, String>
     */
    public Map<String, String> doRequest(String url, SybBaseDTO dto) {
        ChainLog.ChainLogBuilder chainLogBuilder = ChainLog.builder().title("执行请求").paramsLog(String.format("url:%s, dto:%s", url, JsonFormatUtil.toJsonStr(dto)));
        TreeMap<String, String> params = SybUtil.parseEntity(dto);
        MultiValueMap<String, String> multiParams = SybPayUtils.convert(params);
        // 请求头
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(multiParams, headers);
        try {
            ResponseEntity<String> response = restTemplate.exchange(
                    url,
                    HttpMethod.POST,
                    requestEntity,
                    String.class
            );
            String responseBody = response.getBody();
            chainLogBuilder.successLog(JsonFormatUtil.toJsonStr(responseBody));
            return JSONUtil.toBean(responseBody, Map.class);
        } catch (Exception e) {
            chainLogBuilder.errorLog(e);
        }

        chainLogBuilder.build().print();
        return null;
    }
}
